import * as THREE from 'three'
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'
import { FontLoader } from 'three/addons/loaders/FontLoader.js'
import { BufferGeometry, Color, Float32BufferAttribute, Vector3 } from 'three'
import { OrbitControls } from './OrbitControls.js'
import bSpline from './bspline.js'
import { Text } from 'troika-three-text'
import { parseDxfMTextContent } from './parseDxfMTextContent.js'

const textControlCharactersRegex = /\\[AXQWOoLIpfH].*;/g
const curlyBraces = /\\[{}]/g

// Three.js extension functions. Webpack doesn't seem to like it if we modify the THREE object directly.
var THREEx = { Math: {} }
/**
 * Returns the angle in radians of the vector (p1,p2). In other words, imagine
 * putting the base of the vector at coordinates (0,0) and finding the angle
 * from vector (1,0) to (p1,p2).
 * @param  {Object} p1 start point of the vector
 * @param  {Object} p2 end point of the vector
 * @return {Number} the angle
 */
THREEx.Math.angle2 = function (p1, p2) {
  var v1 = new THREE.Vector2(p1.x, p1.y)
  var v2 = new THREE.Vector2(p2.x, p2.y)
  v2.sub(v1) // sets v2 to be our chord
  v2.normalize()
  if (v2.y < 0) return -Math.acos(v2.x)
  return Math.acos(v2.x)
}

THREEx.Math.polar = function (point, distance, angle) {
  var result = {}
  result.x = point.x + distance * Math.cos(angle)
  result.y = point.y + distance * Math.sin(angle)
  return result
}

/**
 * Calculates points for a curve between two points using a bulge value. Typically used in polylines.
 * @param startPoint - the starting point of the curve
 * @param endPoint - the ending point of the curve
 * @param bulge - a value indicating how much to curve
 * @param segments - number of segments between the two given points
 */
function getBulgeCurvePoints(startPoint, endPoint, bulge, segments) {
  var vertex, i, center, p0, p1, angle, radius, startAngle, thetaAngle

  var obj = {}
  obj.startPoint = p0 = startPoint
    ? new THREE.Vector2(startPoint.x, startPoint.y)
    : new THREE.Vector2(0, 0)
  obj.endPoint = p1 = endPoint ? new THREE.Vector2(endPoint.x, endPoint.y) : new THREE.Vector2(1, 0)
  obj.bulge = bulge = bulge || 1

  angle = 4 * Math.atan(bulge)
  radius = p0.distanceTo(p1) / 2 / Math.sin(angle / 2)
  center = THREEx.Math.polar(
    startPoint,
    radius,
    THREEx.Math.angle2(p0, p1) + (Math.PI / 2 - angle / 2)
  )

  obj.segments = segments = segments || Math.max(Math.abs(Math.ceil(angle / (Math.PI / 18))), 6) // By default want a segment roughly every 10 degrees
  startAngle = THREEx.Math.angle2(center, p0)
  thetaAngle = angle / segments

  var vertices = []

  vertices.push(new THREE.Vector3(p0.x, p0.y, 0))

  for (i = 1; i <= segments - 1; i++) {
    vertex = THREEx.Math.polar(center, Math.abs(radius), startAngle + thetaAngle * i)
    vertices.push(new THREE.Vector3(vertex.x, vertex.y, 0))
  }

  return vertices
}

/**
 * Viewer class for a dxf object.
 * @param {Object} data - the dxf object
 * @param {Object} parent - the parent element to which we attach the rendering canvas
 * @param {Number} width - width of the rendering canvas in pixels
 * @param {Number} height - height of the rendering canvas in pixels
 * @param {Object} font - a font loaded with THREE.FontLoader
 * @constructor
 */
export function Viewer(data, parent, width, height, font) {
  createLineTypeShaders(data)

  var scene = new THREE.Scene()

  // Create scene from dxf object (data)
  var i, entity, obj, min_x, min_y, min_z, max_x, max_y, max_z
  var dims = {
    min: { x: 0, y: 0, z: 0 },
    max: { x: 0, y: 0, z: 0 }
  }
  for (i = 0; i < data.entities.length; i++) {
    entity = data.entities[i]
    obj = drawEntity(entity, data)

    if (obj) {
      var bbox = new THREE.Box3().setFromObject(obj)
      if (isFinite(bbox.min.x) && dims.min.x > bbox.min.x) dims.min.x = bbox.min.x
      if (isFinite(bbox.min.y) && dims.min.y > bbox.min.y) dims.min.y = bbox.min.y
      if (isFinite(bbox.min.z) && dims.min.z > bbox.min.z) dims.min.z = bbox.min.z
      if (isFinite(bbox.max.x) && dims.max.x < bbox.max.x) dims.max.x = bbox.max.x
      if (isFinite(bbox.max.y) && dims.max.y < bbox.max.y) dims.max.y = bbox.max.y
      if (isFinite(bbox.max.z) && dims.max.z < bbox.max.z) dims.max.z = bbox.max.z
      scene.add(obj)
    }
    obj = null
  }

  width = width || parent.clientWidth
  height = height || parent.clientHeight
  var aspectRatio = width / height

  var upperRightCorner = { x: dims.max.x, y: dims.max.y }
  var lowerLeftCorner = { x: dims.min.x, y: dims.min.y }

  // Figure out the current viewport extents
  var vp_width = upperRightCorner.x - lowerLeftCorner.x
  var vp_height = upperRightCorner.y - lowerLeftCorner.y
  var center = center || {
    x: vp_width / 2 + lowerLeftCorner.x,
    y: vp_height / 2 + lowerLeftCorner.y
  }

  // Fit all objects into current ThreeDXF viewer
  var extentsAspectRatio = Math.abs(vp_width / vp_height)
  if (aspectRatio > extentsAspectRatio) {
    vp_width = vp_height * aspectRatio
  } else {
    vp_height = vp_width / aspectRatio
  }

  var viewPort = {
    bottom: -vp_height / 2,
    left: -vp_width / 2,
    top: vp_height / 2,
    right: vp_width / 2,
    center: {
      x: center.x,
      y: center.y
    }
  }

  var camera = new THREE.OrthographicCamera(
    viewPort.left,
    viewPort.right,
    viewPort.top,
    viewPort.bottom,
    1,
    19
  )
  camera.position.z = 10
  camera.position.x = viewPort.center.x
  camera.position.y = viewPort.center.y

  var renderer = (this.renderer = new THREE.WebGLRenderer())
  renderer.setSize(width, height)
  renderer.setClearColor(0xfffffff, 0)

  parent.appendChild(renderer.domElement)
  parent.style.display = 'block'

  //TODO: Need to make this an option somehow so others can roll their own controls.
  var controls = new OrbitControls(camera, parent)
  controls.target.x = camera.position.x
  controls.target.y = camera.position.y
  controls.target.z = 0
  controls.zoomSpeed = 1

  //Uncomment this to disable rotation (does not make much sense with 2D drawings).
  //controls.enableRotate = false;

  this.render = function () {
    renderer.render(scene, camera)
  }
  controls.addEventListener('change', this.render)
  this.render()
  controls.update()

  this.resize = function (width, height) {
    var originalWidth = renderer.domElement.width
    var originalHeight = renderer.domElement.height

    var hscale = width / originalWidth
    var vscale = height / originalHeight

    camera.top = vscale * camera.top
    camera.bottom = vscale * camera.bottom
    camera.left = hscale * camera.left
    camera.right = hscale * camera.right

    //        camera.updateProjectionMatrix();

    renderer.setSize(width, height)
    renderer.setClearColor(0xfffffff, 1)
    this.render()
  }

  function drawEntity(entity, data) {
    var mesh
    if (entity.type === 'CIRCLE' || entity.type === 'ARC') {
      mesh = drawArc(entity, data)
    } else if (
      entity.type === 'LWPOLYLINE' ||
      entity.type === 'LINE' ||
      entity.type === 'POLYLINE'
    ) {
      mesh = drawLine(entity, data)
    } else if (entity.type === 'TEXT') {
      mesh = drawText(entity, data)
    } else if (entity.type === 'SOLID') {
      mesh = drawSolid(entity, data)
    } else if (entity.type === 'POINT') {
      mesh = drawPoint(entity, data)
    } else if (entity.type === 'INSERT') {
      mesh = drawBlock(entity, data)
    } else if (entity.type === 'SPLINE') {
      mesh = drawSpline(entity, data)
    } else if (entity.type === 'MTEXT') {
      mesh = drawMtext(entity, data)
    } else if (entity.type === 'ELLIPSE') {
      mesh = drawEllipse(entity, data)
    } else if (entity.type === 'DIMENSION') {
      var dimTypeEnum = entity.dimensionType & 7
      if (dimTypeEnum === 0) {
        mesh = drawDimension(entity, data)
      } else {
        console.log('Unsupported Dimension type: ' + dimTypeEnum)
      }
    } else {
      console.log('Unsupported Entity Type: ' + entity.type)
    }
    return mesh
  }

  function drawEllipse(entity, data) {
    var color = getColor(entity, data)

    var xrad = Math.sqrt(
      Math.pow(entity.majorAxisEndPoint.x, 2) + Math.pow(entity.majorAxisEndPoint.y, 2)
    )
    var yrad = xrad * entity.axisRatio
    var rotation = Math.atan2(entity.majorAxisEndPoint.y, entity.majorAxisEndPoint.x)

    var curve = new THREE.EllipseCurve(
      entity.center.x,
      entity.center.y,
      xrad,
      yrad,
      entity.startAngle,
      entity.endAngle,
      false, // Always counterclockwise
      rotation
    )

    var points = curve.getPoints(50)
    var geometry = new THREE.BufferGeometry().setFromPoints(points)
    var material = new THREE.LineBasicMaterial({ linewidth: 1, color: color })

    // Create the final object to add to the scene
    var ellipse = new THREE.Line(geometry, material)
    return ellipse
  }

  function drawMtext(entity, data) {
    var color = getColor(entity, data)

    if (!font) {
      return console.log('font parameter not set. Ignoring text entity.')
    }

    var textAndControlChars = parseDxfMTextContent(entity.text)

    //Note: We currently only support a single format applied to all the mtext text
    var content = mtextContentAndFormattingToTextAndStyle(textAndControlChars, entity, color)

    var txt = createTextForScene(content.text, content.style, entity, color)

    var group = new THREE.Object3D()
    group.add(txt)
    return group
  }

  function mtextContentAndFormattingToTextAndStyle(textAndControlChars, entity, color) {
    let activeStyle = {
      horizontalAlignment: 'left',
      textHeight: entity.height
    }

    var text = []
    for (let item of textAndControlChars) {
      if (typeof item === 'string') {
        if (item.startsWith('pxq') && item.endsWith(';')) {
          if (item.indexOf('c') !== -1) activeStyle.horizontalAlignment = 'center'
          else if (item.indexOf('l') !== -1) activeStyle.horizontalAlignment = 'left'
          else if (item.indexOf('r') !== -1) activeStyle.horizontalAlignment = 'right'
          else if (item.indexOf('j') !== -1) activeStyle.horizontalAlignment = 'justify'
        } else {
          text.push(item)
        }
      } else if (Array.isArray(item)) {
        var nestedFormat = mtextContentAndFormattingToTextAndStyle(item, entity, color)
        text.push(nestedFormat.text)
      } else if (typeof item === 'object') {
        if (item['S'] && item['S'].length === 3) {
          text.push(item['S'][0] + '/' + item['S'][2])
        } else {
          // not yet supported.
        }
      }
    }
    return {
      text: text.join(),
      style: activeStyle
    }
  }

  function createTextForScene(text, style, entity, color) {
    if (!text) return null

    let textEnt = new Text()
    textEnt.text = text.replaceAll('\\P', '\n').replaceAll('\\X', '\n')

    textEnt.fontSize = style.textHeight
    textEnt.maxWidth = entity.width || Infinity
    textEnt.position.x = entity.position.x
    textEnt.position.y = entity.position.y
    textEnt.position.z = entity.position.z
    textEnt.textAlign = style.horizontalAlignment
    textEnt.color = color
    if (entity.rotation) {
      textEnt.rotation.z = (entity.rotation * Math.PI) / 180
    }
    if (entity.directionVector) {
      var dv = entity.directionVector
      textEnt.rotation.z = new THREE.Vector3(1, 0, 0).angleTo(new THREE.Vector3(dv.x, dv.y, dv.z))
    }
    switch (entity.attachmentPoint) {
      case 1:
        // Top Left
        textEnt.anchorX = 'left'
        textEnt.anchorY = 'top'
        break
      case 2:
        // Top Center
        textEnt.anchorX = 'center'
        textEnt.anchorY = 'top'
        break
      case 3:
        // Top Right
        textEnt.anchorX = 'right'
        textEnt.anchorY = 'top'
        break

      case 4:
        // Middle Left
        textEnt.anchorX = 'left'
        textEnt.anchorY = 'middle'
        break
      case 5:
        // Middle Center
        textEnt.anchorX = 'center'
        textEnt.anchorY = 'middle'
        break
      case 6:
        // Middle Right
        textEnt.anchorX = 'right'
        textEnt.anchorY = 'middle'
        break

      case 7:
        // Bottom Left
        textEnt.anchorX = 'left'
        textEnt.anchorY = 'bottom'
        break
      case 8:
        // Bottom Center
        textEnt.anchorX = 'center'
        textEnt.anchorY = 'bottom'
        break
      case 9:
        // Bottom Right
        textEnt.anchorX = 'right'
        textEnt.anchorY = 'bottom'
        break

      default:
        return undefined
    }

    textEnt.sync(() => {
      if (textEnt.textAlign !== 'left') {
        textEnt.geometry.computeBoundingBox()
        var textWidth = textEnt.geometry.boundingBox.max.x - textEnt.geometry.boundingBox.min.x
        if (textEnt.textAlign === 'center') textEnt.position.x += (entity.width - textWidth) / 2
        if (textEnt.textAlign === 'right') textEnt.position.x += entity.width - textWidth
      }
    })

    return textEnt
  }

  function drawSpline(entity, data) {
    var color = getColor(entity, data)

    var points = getBSplinePolyline(
      entity.controlPoints,
      entity.degreeOfSplineCurve,
      entity.knotValues,
      100
    )

    var geometry = new THREE.BufferGeometry().setFromPoints(points)
    var material = new THREE.LineBasicMaterial({ linewidth: 1, color: color })
    var splineObject = new THREE.Line(geometry, material)

    return splineObject
  }

  /**
   * Interpolate a b-spline. The algorithm examins the knot vector
   * to create segments for interpolation. The parameterisation value
   * is re-normalised back to [0,1] as that is what the lib expects (
   * and t i de-normalised in the b-spline library)
   *
   * @param controlPoints the control points
   * @param degree the b-spline degree
   * @param knots the knot vector
   * @returns the polyline
   */
  function getBSplinePolyline(
    controlPoints,
    degree,
    knots,
    interpolationsPerSplineSegment,
    weights
  ) {
    const polyline = []
    const controlPointsForLib = controlPoints.map(function (p) {
      return [p.x, p.y]
    })

    const segmentTs = [knots[degree]]
    const domain = [knots[degree], knots[knots.length - 1 - degree]]

    for (let k = degree + 1; k < knots.length - degree; ++k) {
      if (segmentTs[segmentTs.length - 1] !== knots[k]) {
        segmentTs.push(knots[k])
      }
    }

    interpolationsPerSplineSegment = interpolationsPerSplineSegment || 25
    for (let i = 1; i < segmentTs.length; ++i) {
      const uMin = segmentTs[i - 1]
      const uMax = segmentTs[i]
      for (let k = 0; k <= interpolationsPerSplineSegment; ++k) {
        const u = (k / interpolationsPerSplineSegment) * (uMax - uMin) + uMin
        // Clamp t to 0, 1 to handle numerical precision issues
        let t = (u - domain[0]) / (domain[1] - domain[0])
        t = Math.max(t, 0)
        t = Math.min(t, 1)
        const p = bSpline(t, degree, controlPointsForLib, knots, weights)
        polyline.push(new THREE.Vector2(p[0], p[1]))
      }
    }
    return polyline
  }

  function drawLine(entity, data) {
    let points = []
    let color = getColor(entity, data)
    var material, lineType, vertex, startPoint, endPoint, bulgeGeometry, bulge, i, line

    if (!entity.vertices) return console.log('entity missing vertices.')

    // create geometry
    for (i = 0; i < entity.vertices.length; i++) {
      if (entity.vertices[i].bulge) {
        bulge = entity.vertices[i].bulge
        startPoint = entity.vertices[i]
        endPoint = i + 1 < entity.vertices.length ? entity.vertices[i + 1] : points[0]

        let bulgePoints = getBulgeCurvePoints(startPoint, endPoint, bulge)

        points.push.apply(points, bulgePoints)
      } else {
        vertex = entity.vertices[i]
        points.push(new THREE.Vector3(vertex.x, vertex.y, 0))
      }
    }
    if (entity.shape) points.push(points[0])

    // set material
    if (entity.lineType) {
      lineType = data.tables.lineType.lineTypes[entity.lineType]
    }

    if (lineType && lineType.pattern && lineType.pattern.length !== 0) {
      material = new THREE.LineDashedMaterial({ color: color, gapSize: 4, dashSize: 4 })
    } else {
      material = new THREE.LineBasicMaterial({ linewidth: 1, color: color })
    }

    var geometry = new BufferGeometry().setFromPoints(points)

    line = new THREE.Line(geometry, material)
    return line
  }

  function drawArc(entity, data) {
    var startAngle, endAngle
    if (entity.type === 'CIRCLE') {
      startAngle = entity.startAngle || 0
      endAngle = startAngle + 2 * Math.PI
    } else {
      startAngle = entity.startAngle
      endAngle = entity.endAngle
    }

    var curve = new THREE.ArcCurve(0, 0, entity.radius, startAngle, endAngle)

    var points = curve.getPoints(32)
    var geometry = new THREE.BufferGeometry().setFromPoints(points)

    var material = new THREE.LineBasicMaterial({ color: getColor(entity, data) })

    var arc = new THREE.Line(geometry, material)
    arc.position.x = entity.center.x
    arc.position.y = entity.center.y
    arc.position.z = entity.center.z

    return arc
  }

  function addTriangleFacingCamera(verts, p0, p1, p2) {
    // Calculate which direction the points are facing (clockwise or counter-clockwise)
    var vector1 = new Vector3()
    var vector2 = new Vector3()
    vector1.subVectors(p1, p0)
    vector2.subVectors(p2, p0)
    vector1.cross(vector2)

    var v0 = new Vector3(p0.x, p0.y, p0.z)
    var v1 = new Vector3(p1.x, p1.y, p1.z)
    var v2 = new Vector3(p2.x, p2.y, p2.z)

    // If z < 0 then we must draw these in reverse order
    if (vector1.z < 0) {
      verts.push(v2, v1, v0)
    } else {
      verts.push(v0, v1, v2)
    }
  }

  function drawSolid(entity, data) {
    var material,
      verts,
      geometry = new THREE.BufferGeometry()

    var points = entity.points
    // verts = geometry.vertices;
    verts = []
    addTriangleFacingCamera(verts, points[0], points[1], points[2])
    addTriangleFacingCamera(verts, points[1], points[2], points[3])

    material = new THREE.MeshBasicMaterial({ color: getColor(entity, data) })
    geometry.setFromPoints(verts)

    return new THREE.Mesh(geometry, material)
  }

  function drawText(entity, data) {
    var geometry, material, text

    if (!font)
      return console.warn(
        'Text is not supported without a Three.js font loaded with THREE.FontLoader! Load a font of your choice and pass this into the constructor. See the sample for this repository or Three.js examples at http://threejs.org/examples/?q=text#webgl_geometry_text for more details.'
      )

    geometry = new TextGeometry(entity.text.replaceAll('%%u', ''), {
      font: font,
      depth: 0,
      size: entity.textHeight || 12
    })

    if (entity.rotation) {
      var zRotation = (entity.rotation * Math.PI) / 180
      geometry.rotateZ(zRotation)
    }

    material = new THREE.MeshBasicMaterial({
      color: getColor(entity, data),
      side: THREE.DoubleSide // 正交相机 需要双面
    })

    text = new THREE.Mesh(geometry, material)
    text.position.x = entity.startPoint.x
    text.position.y = entity.startPoint.y
    text.position.z = entity.startPoint.z

    return text
  }

  function drawPoint(entity, data) {
    var geometry, material, point

    geometry = new THREE.BufferGeometry()

    geometry.setAttribute(
      'position',
      new Float32BufferAttribute([entity.position.x, entity.position.y, entity.position.z], 3)
    )

    var color = getColor(entity, data)

    material = new THREE.PointsMaterial({ size: 0.1, color: new Color(color) })
    point = new THREE.Points(geometry, material)
    scene.add(point)
  }

  function drawDimension(entity, data) {
    var block = data.blocks[entity.block]

    if (!block || !block.entities) return null

    var group = new THREE.Object3D()
    // if(entity.anchorPoint) {
    //     group.position.x = entity.anchorPoint.x;
    //     group.position.y = entity.anchorPoint.y;
    //     group.position.z = entity.anchorPoint.z;
    // }

    for (var i = 0; i < block.entities.length; i++) {
      var childEntity = drawEntity(block.entities[i], data, group)
      if (childEntity) group.add(childEntity)
    }

    return group
  }

  function drawBlock(entity, data) {
    var block = data.blocks[entity.name]

    if (!block.entities) return null

    var group = new THREE.Object3D()

    if (entity.xScale) group.scale.x = entity.xScale
    if (entity.yScale) group.scale.y = entity.yScale

    if (entity.rotation) {
      group.rotation.z = (entity.rotation * Math.PI) / 180
    }

    if (entity.position) {
      group.position.x = entity.position.x
      group.position.y = entity.position.y
      group.position.z = entity.position.z
    }

    for (var i = 0; i < block.entities.length; i++) {
      var childEntity = drawEntity(block.entities[i], data, group)
      if (childEntity) group.add(childEntity)
    }

    return group
  }

  function getColor(entity, data) {
    var color = 0x000000 //default
    if (entity.color) color = entity.color
    else if (data.tables && data.tables.layer && data.tables.layer.layers[entity.layer])
      color = data.tables.layer.layers[entity.layer].color

    if (color == null || color === 0xffffff) {
      color = 0x000000
    }
    return color
  }

  function createLineTypeShaders(data) {
    var ltype, type
    if (!data.tables || !data.tables.lineType) return
    var ltypes = data.tables.lineType.lineTypes

    for (type in ltypes) {
      ltype = ltypes[type]
      if (!ltype.pattern) continue
      ltype.material = createDashedLineShader(ltype.pattern)
    }
  }

  function createDashedLineShader(pattern) {
    var i,
      dashedLineShader = {},
      totalLength = 0.0

    for (i = 0; i < pattern.length; i++) {
      totalLength += Math.abs(pattern[i])
    }

    dashedLineShader.uniforms = THREE.UniformsUtils.merge([
      THREE.UniformsLib['common'],
      THREE.UniformsLib['fog'],

      {
        pattern: { type: 'fv1', value: pattern },
        patternLength: { type: 'f', value: totalLength }
      }
    ])

    dashedLineShader.vertexShader = [
      'attribute float lineDistance;',

      'varying float vLineDistance;',

      THREE.ShaderChunk['color_pars_vertex'],

      'void main() {',

      THREE.ShaderChunk['color_vertex'],

      'vLineDistance = lineDistance;',

      'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',

      '}'
    ].join('\n')

    dashedLineShader.fragmentShader = [
      'uniform vec3 diffuse;',
      'uniform float opacity;',

      'uniform float pattern[' + pattern.length + '];',
      'uniform float patternLength;',

      'varying float vLineDistance;',

      THREE.ShaderChunk['color_pars_fragment'],
      THREE.ShaderChunk['fog_pars_fragment'],

      'void main() {',

      'float pos = mod(vLineDistance, patternLength);',

      'for ( int i = 0; i < ' + pattern.length + '; i++ ) {',
      'pos = pos - abs(pattern[i]);',
      'if( pos < 0.0 ) {',
      'if( pattern[i] > 0.0 ) {',
      'gl_FragColor = vec4(1.0, 0.0, 0.0, opacity );',
      'break;',
      '}',
      'discard;',
      '}',

      '}',

      THREE.ShaderChunk['color_fragment'],
      THREE.ShaderChunk['fog_fragment'],

      '}'
    ].join('\n')

    return dashedLineShader
  }

  function findExtents(scene) {
    for (var child of scene.children) {
      var minX, maxX, minY, maxY
      if (child.position) {
        minX = Math.min(child.position.x, minX)
        minY = Math.min(child.position.y, minY)
        maxX = Math.max(child.position.x, maxX)
        maxY = Math.max(child.position.y, maxY)
      }
    }

    return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } }
  }
}

let font = null

const loader = new FontLoader()

export default async function threeDxfViewer(data, parent, width, height, fontUrl) {
  if (!font && fontUrl) {
    font = await new Promise((resolve) => {
      loader.load(fontUrl, (res) => resolve(res))
    })
  }

  return new Viewer(data, parent, width, height, font)
}
window.threeDxfViewer = threeDxfViewer
// Show/Hide helpers from https://plainjs.com/javascript/effects/hide-or-show-an-element-42/
// get the default display style of an element
function defaultDisplay(tag) {
  var iframe = document.createElement('iframe')
  iframe.setAttribute('frameborder', 0)
  iframe.setAttribute('width', 0)
  iframe.setAttribute('height', 0)
  document.documentElement.appendChild(iframe)

  var doc = (iframe.contentWindow || iframe.contentDocument).document

  // IE support
  doc.write()
  doc.close()

  var testEl = doc.createElement(tag)
  doc.documentElement.appendChild(testEl)
  var display = (window.getComputedStyle ? getComputedStyle(testEl, null) : testEl.currentStyle)
    .display
  iframe.parentNode.removeChild(iframe)
  return display
}

// actual show/hide function used by show() and hide() below
function showHide(el, show) {
  var value = el.getAttribute('data-olddisplay'),
    display = el.style.display,
    computedDisplay = (window.getComputedStyle ? getComputedStyle(el, null) : el.currentStyle)
      .display

  if (show) {
    if (!value && display === 'none') el.style.display = ''
    if (el.style.display === '' && computedDisplay === 'none')
      value = value || defaultDisplay(el.nodeName)
  } else {
    if ((display && display !== 'none') || !(computedDisplay == 'none'))
      el.setAttribute('data-olddisplay', computedDisplay == 'none' ? display : computedDisplay)
  }
  if (!show || el.style.display === 'none' || el.style.display === '')
    el.style.display = show ? value || '' : 'none'
}

// helper functions
function show(el) {
  showHide(el, true)
}
function hide(el) {
  showHide(el)
}
